# 获取教程、习题、案例，共同学习、讨论、打卡
# 请关注：Crossin的编程教室
# 学习交流或对代码有疑问请加交流群：
#【微信】sunset24678（添加时备注：python）
#【QQ】155816967（最新群号见 https://python666.cn/c/9）

import tkinter as tk
import requests
import json
import random
import re
import threading
import queue

# =========================================
# 获取API token
# 注意！！！此处需要去百度千帆平台注册获取
API_KEY = "填上你的API_KEY"
SECRET_KEY = "填上你的SECRET_KEY"

# 获取千帆平台token（官方代码）
def get_access_token():
    """
    使用 AK，SK 生成鉴权签名（Access Token）
    :return: access_token，或是None(如果错误)
    """
    url = "https://aip.baidubce.com/oauth/2.0/token"
    params = {"grant_type": "client_credentials", "client_id": API_KEY, "client_secret": SECRET_KEY}
    return str(requests.post(url, params=params).json().get("access_token"))

access_token = get_access_token()

# AI抽象类，记录每个AI的编号（每轮随机分配）、状态、头像
class AIBot():
    headers = {'Content-Type': 'application/json'}

    def __init__(self, number):
        self.number = number  # 编号
        self.active = True
        self.avatar = tk.PhotoImage(file=f"AI{self.index}.png")

    # 调用大模型API
    def callAPI(self, text):
        payload = json.dumps({
            "messages": [
                {
                    "role": "user",
                    "content": text
                }
            ]
        })
        print(self.url)
        response = requests.request("POST", self.url, headers=self.headers, data=payload)
        resp = response.json()
        return resp['result']

    def chat(self, question):
        answer = self.callAPI(question)
        return answer

# 5个不同的AI子类，区分不同的名字和调用接口
class AIBot_ERNIEBot4(AIBot):
    url = "https://aip.baidubce.com/rpc/2.0/ai_custom/v1/wenxinworkshop/chat/completions_pro?access_token=" + access_token
    name = 'ERNIEBot4'
    index = 1

class AIBot_BLOOMZ(AIBot):
    url = "https://aip.baidubce.com/rpc/2.0/ai_custom/v1/wenxinworkshop/chat/bloomz_7b1?access_token=" + access_token
    name = 'BLOOMZ'
    index = 2

class AIBot_QCLlama2(AIBot):
    url = "https://aip.baidubce.com/rpc/2.0/ai_custom/v1/wenxinworkshop/chat/qianfan_chinese_llama_2_7b?access_token=" + access_token
    name = 'QCLlama2'
    index = 3

class AIBot_ChatGLM2(AIBot):
    url = "https://aip.baidubce.com/rpc/2.0/ai_custom/v1/wenxinworkshop/chat/chatglm2_6b_32k?access_token=" + access_token
    name = 'ChatGLM2'
    index = 4

class AIBot_AquilaChat(AIBot):
    url = "https://aip.baidubce.com/rpc/2.0/ai_custom/v1/wenxinworkshop/chat/aquilachat_7b?access_token=" + access_token
    name = 'AquilaChat'
    index = 5

# GUI类，游戏的主体部分
class WhoIsNotAI:
    def __init__(self, root):
        # 初始化游戏数据
        self.init_game()
        self.game = None
        self.q = queue.Queue()
        self.avatar = tk.PhotoImage(file="head.png")
        self.forbid_img = tk.PhotoImage(file="forbid.png")

        # 初始化界面
        self.root = root
        self.root.title("谁是人类？")
        self.root.geometry(f"700x680")

        # 上部分：头像栏
        self.ai_frame = tk.Frame(root, bg="#e0e0e0", padx=10, pady=10)
        self.ai_frame.pack(side=tk.TOP, fill=tk.X)
        self.forbid_labels = []

        for i in range(5):
            avatar_frame = tk.Canvas(self.ai_frame)

            # 上方Label信息
            top_label1 = tk.Label(avatar_frame, text="编号", bg="#c0e0f0")
            top_label1.grid(row=0, column=0, sticky="e")

            top_label2 = tk.Label(avatar_frame, text=self.bots[i].number)
            top_label2.grid(row=0, column=1, sticky="w")

            top_label3 = tk.Label(avatar_frame, text=self.bots[i].name)
            top_label3.grid(row=0, column=2, columnspan=2, sticky="ew")

            # 下方Label信息
            bottom_label1 = tk.Label(avatar_frame, text="得票", bg="#f0c0c0")
            bottom_label1.grid(row=2, column=0, sticky="e")

            bottom_label2 = tk.Label(avatar_frame, textvariable=self.voteVars[self.bots[i].number-1])
            bottom_label2.grid(row=2, column=1, sticky="w")

            bottom_label3 = tk.Label(avatar_frame, text="猜中", bg="#f0f0c0")
            bottom_label3.grid(row=3, column=0, sticky="e")

            bottom_label4 = tk.Label(avatar_frame, textvariable=self.bingoVars[self.bots[i].number-1])
            bottom_label4.grid(row=3, column=1, sticky="w")

            avatar_label = tk.Label(avatar_frame, image=self.bots[i].avatar)
            avatar_label.grid(row=1, rowspan=3, column=2)

            # 淘汰图标
            self.forbid_labels.append(tk.Label(avatar_frame, image=self.forbid_img))

            avatar_frame.grid(row=0, column=i, padx=5)

        # 中部分：对话栏
        self.chat_frame = tk.Frame(root)
        self.chat_frame.pack(side=tk.TOP, fill=tk.BOTH, expand=True)

        self.scrollbar = tk.Scrollbar(self.chat_frame, orient=tk.VERTICAL)
        self.scrollbar.pack(side=tk.RIGHT, fill=tk.Y)

        self.chat_canvas = tk.Canvas(self.chat_frame, yscrollcommand=self.scrollbar.set)
        self.chat_canvas.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)

        # 创建一个Frame作为Canvas的子控件，用于放置对话文本
        self.chat_content_frame = tk.Frame(self.chat_canvas)
        self.chat_canvas.create_window((0, 0), window=self.chat_content_frame, anchor=tk.NW)

        # 添加一些测试数据
        # for i in range(20):
        #     message = f"对话{i + 1}: 这是一条测试消息。"  # 测试数据
        #     label = tk.Label(self.chat_content_frame, text=message, wraplength=400, anchor="w", justify="left", bg="white")
        #     label.pack(fill=tk.X, pady=5)
        # for i in range(2):
        #     speaker_image = self.resized_avatar_image
        #     message = f"对{i + 1}话: \n这是一条测试这是一条测试这是一条测试这是一条测试这是一条测试试这是一条测试这是一条测试试这是一条测试这是一条测试试这是一条测试这是一条测试试这是一条测试这是一条测试\n消\n息。"  # 测试数据
        #
        #     # 在对话栏中添加新的头像和消息
        #     self.add_message(speaker_image, message)

        # 游戏开始按钮
        self.start_button = tk.Button(self.chat_content_frame, text="游戏开始", command=self.game_start, height=5, width=20)
        self.start_button.pack(padx=240, pady=200)

        # 将Canvas绑定鼠标滚轮事件，以支持滚轮滚动
        self.chat_content_frame.bind_all("<MouseWheel>", self.on_mousewheel)

        # 设置Canvas的滚动范围
        self.chat_canvas.update_idletasks()
        bbox = self.chat_canvas.bbox(tk.ALL)
        self.chat_canvas.config(scrollregion=bbox)

        # 下部分：发言栏
        self.input_frame = tk.Frame(root, bg="#e0e0e0", height=80, padx=10, pady=10)  # 设置背景色、高度和padding
        self.input_frame.pack(side=tk.BOTTOM, fill=tk.X)

        # 第一行：编号
        number_label = tk.Label(self.input_frame, text="编号", bg="#c0e0f0", padx=5)
        number_label.grid(row=0, column=0, sticky=tk.E)

        number_value = tk.Label(self.input_frame, text=self.human)
        number_value.grid(row=0, column=1, sticky=tk.W)

        # 第二行：得票
        votes_label = tk.Label(self.input_frame, text="得票", bg="#f0c0c0", padx=5)
        votes_label.grid(row=1, column=0, sticky=tk.E)

        votes_value = tk.Label(self.input_frame, textvariable=self.voteVars[self.human-1])
        votes_value.grid(row=1, column=1, sticky=tk.W)

        # 输入框和发送按钮
        self.input_text = tk.Text(self.input_frame, wrap=tk.WORD, height=3, width=72)  # 多行的Entry
        self.input_text.grid(row=0, column=2, rowspan=2, sticky=tk.W + tk.E, padx=10, pady=0)  # 设置padding

        self.send_button = tk.Button(self.input_frame, text="发送", command=self.get_input, height=3, width=4)  # 设置按钮高度
        self.send_button.grid(row=0, column=3, rowspan=2, sticky=tk.W)  # 设置padding
        self.send_button.config(state=tk.DISABLED)

    def on_mousewheel(self, event):
        # 鼠标滚轮事件处理
        self.chat_canvas.yview_scroll(int(-event.delta), "units")

    def update_dialog(self):
        # 设置Canvas的滚动范围
        self.chat_canvas.update_idletasks()
        bbox = self.chat_canvas.bbox(tk.ALL)
        self.chat_canvas.config(scrollregion=bbox)
        # 滚动到最底部显示最新消息
        self.chat_canvas.yview_moveto(1.0)

    def add_message(self, speaker_image, speaker_name, message):
        # 向中部对话栏添加消息的函数
        dialog_frame = tk.Frame(self.chat_content_frame)

        # 在对话栏中添加新的头像和消息
        speaker_label = tk.Label(dialog_frame, image=speaker_image)
        speaker_label.pack(side=tk.LEFT, anchor=tk.NW, padx=5, pady=5)

        name_label = tk.Label(dialog_frame, text=speaker_name)
        name_label.pack(side=tk.TOP, anchor=tk.NW)

        message_label = tk.Label(dialog_frame, text=message, wraplength=500, justify="left", bg="white", padx=10, pady=10)
        message_label.pack(anchor=tk.NW)

        dialog_frame.pack(fill=tk.X, pady=5)

    def add_bot_message(self, bot, message):
        # 向中部对话栏添加AI发送的消息
        self.add_message(bot.avatar, bot.name, message)

    def add_human_message(self, message):
        # 向中部对话栏添加人类发送的消息
        self.add_message(self.avatar, 'Crossin', message)

    def add_sys_message(self, message):
        # 向中部对话栏添加系统消息
        dialog_frame = tk.Frame(self.chat_content_frame)

        message_label = tk.Label(dialog_frame, text=message, wraplength=500, justify="left", padx=10, pady=10)
        message_label.pack(anchor=tk.NW, padx=(80, 0))

        dialog_frame.pack(fill=tk.X, pady=5)

        self.update_dialog()

    def get_input(self):
        # 获取人类输入
        message = self.input_text.get("1.0", tk.END).strip()
        if message:
            self.input_text.delete("1.0", tk.END)
            self.send_button.config(state=tk.DISABLED)
            # 向队列发取数据
            self.q.put(message)

    def init_game(self):
        # 初始化AI
        self.counts = 6
        numbers = list(range(1, self.counts + 1))
        random.shuffle(numbers)
        self.human = numbers.pop()
        self.bots = []
        self.bots.append(AIBot_ERNIEBot4(numbers.pop()))
        self.bots.append(AIBot_BLOOMZ(numbers.pop()))
        self.bots.append(AIBot_QCLlama2(numbers.pop()))
        self.bots.append(AIBot_ChatGLM2(numbers.pop()))
        self.bots.append(AIBot_AquilaChat(numbers.pop()))

        self.votes = [0] * self.counts   # 记录得票数
        self.bingo = [0] * self.counts   # 记录猜中数量
        self.voteVars = []               # 同步界面显示投票数的变量类
        self.bingoVars = []              # 同步界面显示猜中数的变量类
        for v in self.votes:
            self.voteVars.append(tk.IntVar())
            self.bingoVars.append(tk.IntVar())

    def game_loop(self):
        self.start_button.destroy()
        # 游戏主循环
        host = 0
        index = 0
        while self.counts > 1:
            # AI轮流选择话题
            index += 1
            host = (host - 1) % len(self.bots)
            self.add_sys_message(f'【第{index}轮，剩余{self.counts}人】\n请{self.bots[host].number}号玩家{self.bots[host].name}选择本轮话题...')
            title_prompt = '请为一场圆桌讨论会选择一个讨论的话题，在20字以内。直接给我话题内容，不要加其他说明'
            title = self.bots[host].chat(title_prompt)
            self.add_bot_message(self.bots[host], title)

            # 所有AI作答
            answers = {}
            for bot in self.bots:
                if bot.active:
                    self.add_sys_message(f'\n\n玩家{bot.number}作答中...')
                    answer = bot.chat(f'{title}（回答控制在140字以内）')
                    answers[bot.number] = answer
                    self.add_bot_message(bot, answer)

            # 人类作答
            self.add_sys_message(f'\n\n玩家{self.human}作答中...')
            self.send_button.config(state=tk.NORMAL)
            # 向队列中发数据
            answers[self.human] = self.q.get()
            self.add_human_message(answers[self.human])

            # 汇总回答
            self.add_sys_message('我们中间出现了一个人类，你认为是谁呢？')
            answers_list = sorted(answers.items())
            judge_prompt = '''你是一个AI游戏的参赛玩家，现在要从以下几位玩家发言中，选出你认为是最可能人类伪装的一位AI玩家，并给出你的理由。

你的回答的第一句必须是以下格式：
“我认为玩家{}是人类。”
{}替换为你认为人类伪装的玩家发言前面的编号。

以下是各位玩家的发言：
'''

            for answer in answers_list:
                judge_prompt += '玩家%d：“%s”\n\n' % answer
            print(judge_prompt)

            # AI进行投票
            rex = re.compile(r'玩家(\d)')
            for bot in self.bots:
                if bot.active:
                    self.add_sys_message(f'\n\n{bot.number}号玩家{bot.name}判断中...')
                    judge = bot.chat(judge_prompt)
                    self.add_bot_message(bot, judge)
                    try:
                        # 提取投票结果
                        num = int(rex.search(judge).group(1))
                        print(f'选择【{num}】')
                        if self.votes[num - 1] >= 0:
                            self.votes[num - 1] += 1  # 记录得票
                        else:
                            self.votes[num - 1] -= 1
                        if num == self.human:
                            self.bingo[bot.number - 1] += 1  # 记录猜中次数
                    except Exception as e:
                        print('Error:', e)

            # 同步界面显示
            print(f'{self.votes=}')
            print(f'{self.bingo=}')
            for i in range(len(self.votes)):
                self.voteVars[i].set(abs(self.votes[i]))
                self.bingoVars[i].set(self.bingo[i])

            # 根据投票结果判断出局
            m = max(self.votes)
            if self.votes.count(m) == 1:
                # 如果存在唯一最高票
                n = self.votes.index(m)
                self.votes[n] *= -1
                if n + 1 != self.human:
                    # 选中AI出局
                    for i, b in enumerate(self.bots):
                        if b.number == n + 1:
                            b.active = False
                            self.counts -= 1
                            self.add_sys_message(f'\n\n{b.number}号玩家{b.name}出局。游戏继续！')
                            self.forbid_labels[i].grid(row=1, column=2, rowspan=2)
                            break
                else:
                    # 游戏结束
                    self.add_sys_message('人类出局。游戏结束！')
                    break

        else:
            # 通关
            self.add_sys_message('人类胜利。欧耶！')

    def game_start(self):
        self.start_button.destroy()
        self.game = threading.Thread(target=self.game_loop, args=())
        self.game.daemon = True
        self.game.start()


if __name__ == "__main__":
    root = tk.Tk()
    app = WhoIsNotAI(root)
    root.mainloop()
